#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the version 3 of the GNU Lesser General Public License
#   as published by the Free Software Foundation.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU Lesser General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Copyright (c) NEC Deutschland GmbH, NEC HPC Europe
#
# $Id$
#
#from inspect import isclass, isfunction, ismethod
from inspect import isclass
from sys import stderr
import types
from lru_cache import LRUCache


__all_ = ["Factory"]


class Factory():
    """
    Factory for empty objects. Needed when deserializing objects.
    """
    __factories = {}
    __context_functions = {}

    @staticmethod
    def set_factory( obj_type, function, package, abstract = False ):
        assert obj_type is not None and function is not None, "wrong arguments"
        # the following line showed malfunction when compiled with Cython
        #assert isclass( function ) or ismethod( function ) or isfunction( function ), \
        assert callable( function ), \
                    "%s factory method is not callable (%s)" % (obj_type, str( type( function ) ))
        Factory.__factories[obj_type] = (function, package, abstract)

    @staticmethod
    def build( obj_type, context=None, *args, **kwds ):
        "It should be possible to build a fresh object when passing no arguments at all."
        if not obj_type in Factory.__factories:
            print >>stderr, "Failed to build %s object: type not in factory!" % obj_type
            return None
        f, __pkg, abstract = Factory.__factories[obj_type]
        if abstract:
            print >>stderr, "Cannot build '%s' object. Class is abstract" % obj_type
            return None
        try:
            res = f( *args, **kwds )
        except Exception, e:
            print >>stderr, "Failed to build '%s' object with parameters: %s, %s" % (obj_type, str( args ), str( kwds ))
            print str( e )
            return None
        if context is not None:
            # inject a function into the object that returns the context.
            cstring = str( context )
            func = None
            if cstring in Factory.__context_functions:
                # Function has been declared already for some other object. Use it!
                func = Factory.__context_functions[cstring]
            else:
                ctxt = context
                # We need a new function. Here it comes:
                def get_context( obj ):
                    return ctxt
                func = get_context
                Factory.__context_functions[cstring] = get_context
            res.get_context = types.MethodType( func, res, res.__class__ )
        return res

    @staticmethod
    def contains_class( obj_type ):
        return obj_type in Factory.__factories

    @staticmethod
    def can_build( obj_type, *args, **kwds ):
        return obj_type in Factory.__factories and not Factory.__factories[ obj_type ][2]

    @staticmethod
    def get_class( obj_type ):
        if not obj_type in Factory.__factories:
            print >>stderr, "Failed to build %s object: type not in factory!" % obj_type
            return None
        f, __pkg, abstract = Factory.__factories[obj_type]
        if isclass( f ):
            return f
        else:
            return None.__class__

    @staticmethod
    def get_derived_classes( obj_types ):
        base_classes = []
        for name in obj_types:
            if not name in Factory.__factories:
                print >>stderr, "Failed to find %s object: type not in factory!" % name
                return None
            f, __pkg, abstract = Factory.__factories[name]
            base_classes.append( f )
        base_classes = tuple( base_classes )
        result = {}
        for name, ( f, __pkg, abstract ) in Factory.__factories.iteritems():
            if not abstract and f.__class__ is types.TypeType and issubclass( f, base_classes ):
                result[ name ] = f
        return result

    @staticmethod
    def import_all():
        cmds = []
        for obj_type in Factory.__factories.keys():
            f, pkg, abstract = Factory.__factories[obj_type]
            if pkg is not None:
                cmds.append( "from %(pkg)s import %(obj_type)s" % locals() )
        return cmds

    @staticmethod
    def import_class( obj_type ):
        if obj_type in Factory.__factories:
            f, pkg, abstract = Factory.__factories[obj_type]
            if pkg is not None:
                return "from %(pkg)s import %(obj_type)s" % locals()
        return None
